#include "Precomp.h"
#include "ltMapState.h"
#include "ltMapObject.h"
#include "ltPluggableFactory.h"
#include "ltGraphicManager.h"
#include "mapformat.pb.h"
#include "OgreODE/OgreODE_Core.h"

#include <boost/bind.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/construct.hpp>
#include <algorithm>

using namespace boost;
using namespace std;

namespace Lotus 
{
	MapState::MapState()
	:deleteOnLeave(true),updateTimer(0.f),interval(0.0333f),physicWorld(0),mStepper(0)
	{
	}

	MapState::~MapState()
	{
		std::for_each(
			objects.begin(),
			objects.end(),
			lambda::bind(lambda::delete_ptr(),lambda::_1));//delete all objects	
		if(mStepper)
			delete mStepper;
		if(physicWorld)
			delete physicWorld;
	}

	MapObject* MapState::createObject(const char* className,MapObject* parent)
	{
		MapObject* object=(MapObject*)(PluggableFactory::getInstance().create(className));
		if(!object)
			return NULL;
		object->map=this;
		object->onCreate();//call the "polymorphic constructor"
		if(parent)//if the object needs a parent
		{//add it to the parent
			object->parent=parent;
			parent->childObjectList.push_back(object);
			parent->onChildAttached(object);
		}
		else//add it to this map
			addObject(object);
		return object;
	}

	MapObject* MapState::getObject(const char* name)
	{
		std::map<std::string,MapObject*>::const_iterator itr = objectWidthNames.find(std::string(name));
		if(itr != objectWidthNames.end())
			return itr->second;
		return NULL;
	}

	void MapState::destroyObject(MapObject* object)
	{
		MapObject* parent=object->parent;
		setActive(object,false);
		if(parent)
		{//if it has a parent
			parent->childObjectList.remove(object);
			parent->onChildDetached(object);
			delete object;
		}
		else
		{//if it is in the map
			removeObject(object);
			delete object;
		}
	}

	void MapState::addObject(MapObject* object)
	{
		objects.push_back(object);
	}

	void MapState::removeObject(MapObject* object)
	{
		objects.remove(object);
	}

	void MapState::iterateObjects(MapObjectIterator* iterator)
	{
		foreach(MapObject* object,objects)
		{
			bool next=iterator->iterate(object);
			if(!next)
				return;
		}
	}

	void MapState::setActive(MapObject* object, bool active)
	{
		if(active&&!(object->active))//turn on
		{
			object->updateConnection=updateSignal.connect(boost::bind(&MapObject::onUpdate,object),*object);
		}
		else if(!active&&(object->active))//turn off
		{
			object->updateConnection.disconnect();
		}
		object->active=active;
	}

	bool MapState::isActive(MapObject* object) const
	{
		return object->active;
	}

	void MapState::saveObject(MapObject* object,mapformat::MapObject* objectData) const
	{
		PluggableFactory& factory=PluggableFactory::getInstance();
		const char* typeName=factory.getProductName(object);//retrive the typename from the factory
		objectData->set_type(typeName);//set type name
		if(object->getName().compare("") != 0)
			objectData->set_name(object->getName());
		mapformat::Vector* vec;
		Ogre::Vector3 vector=object->getPosition();
		vec=objectData->mutable_position();
		vec->set_x(vector.x);
		vec->set_y(vector.y);
		vec->set_z(vector.z);
		vector=object->getRotation();
		vec=objectData->mutable_rotation();
		vec->set_x(vector.x);
		vec->set_y(vector.y);
		vec->set_z(vector.z);
		vector=object->getScale();
		vec=objectData->mutable_scale();
		vec->set_x(vector.x);
		vec->set_y(vector.y);
		vec->set_z(vector.z);
		foreach(MapObject* child,(object->childObjectList))
		{
			mapformat::MapObject* childData=objectData->add_children();
			saveObject(child,childData);
		}
	}

	MapObject* MapState::loadObject(mapformat::MapObject* objectData,MapObject* parent)
	{
		MapObject* object=createObject(objectData->type().c_str(),parent);
		if(!object)
		{
			return object;
		}
		if(objectData->has_name())
			object->setName(objectData->name().c_str());
		mapformat::Vector vector=objectData->position();
		object->setPosition(Ogre::Vector3(vector.x(),vector.y(),vector.z()));
		vector=objectData->rotation();
		object->setRotation(Ogre::Vector3(vector.x(),vector.y(),vector.z()));
		vector=objectData->scale();
		object->setScale(Ogre::Vector3(vector.x(),vector.y(),vector.z()));
		::google::protobuf::RepeatedPtrField< ::Lotus::mapformat::MapObject >* children=objectData->mutable_children();
		foreach(mapformat::MapObject& child,*children)
		{
			loadObject(&child,object);
		}
		return object;
	}

	OgreOde::World* MapState::getPhysicWorld() const
	{
		return physicWorld;
	}

	void MapState::onCreate()
	{
	}

	void MapState::onEnter()
	{
		std::for_each(objects.begin(),objects.end(),lambda::bind(&MapObject::onMapBegin,lambda::_1));
	}

	void MapState::onLeave()
	{
		std::for_each(objects.begin(),objects.end(),lambda::bind(&MapObject::onMapEnd,lambda::_1));
		if(deleteOnLeave)
			delete this;
	}

	void MapState::setUpdateInterval(Ogre::Real interval)
	{
		this->interval=interval;
	}

	void MapState::update(Ogre::Real timeSinceLastFrame)
	{
		if(mStepper->step(timeSinceLastFrame))
			physicWorld->synchronise();
		FrameEvent(timeSinceLastFrame);//fire frame event for high-priority task
		updateTimer+=timeSinceLastFrame;
		if(updateTimer>=interval)
		{
			updateTimer-=interval;
			updateSignal();//update entities(low-priority)
		}
	}

	bool MapState::collision(OgreOde::Contact* contact)
	{
		Collider* firstCollider = static_cast<Collider*>(contact->getFirstGeometry()->getUserObject());
		Collider* secondCollider = static_cast<Collider*>(contact->getSecondGeometry()->getUserObject());

		if((firstCollider != NULL) && (secondCollider != NULL))
		{
			if((firstCollider->listenFlag & secondCollider->collisionFlag) > 0)
				firstCollider->onCollision(contact);
			if((secondCollider->listenFlag & firstCollider->collisionFlag) > 0)
				secondCollider->onCollision(contact);
		}
		return true;
	}
}